/*
 * Copyright 2011 Greg Milette and Adam Stroud
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package at.the.gogo.parkoid.sensors.proximity;

import android.app.Service;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

/**
 * Service that provides an alternative implementation for a proximity alert.
 * 
 * @author Adam Stroud &#60;<a
 *         href="mailto:adam.stroud@gmail.com">adam.stroud@gmail.com</a>&#62;
 */
public class ProximityAlertService extends Service implements LocationListener {
    public static final String  LATITUDE_INTENT_KEY  = "LATITUDE_INTENT_KEY";
    public static final String  LONGITUDE_INTENT_KEY = "LONGITUDE_INTENT_KEY";
    public static final String  RADIUS_INTENT_KEY    = "RADIUS_INTENT_KEY";
    private static final String TAG                  = "ProximityAlertService";

    private double              latitude;
    private double              longitude;
    private float               radius;
    private LocationManager     locationManager;
    private boolean             inProximity;

    @Override
    public IBinder onBind(final Intent intent) {
        // no-op
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
    }

    @Override
    public int onStartCommand(final Intent intent, final int flags,
            final int startId) {
        Location bestLocation = null;

        latitude = intent.getDoubleExtra(LATITUDE_INTENT_KEY, Double.MIN_VALUE);
        longitude = intent.getDoubleExtra(LONGITUDE_INTENT_KEY,
                Double.MIN_VALUE);
        radius = intent.getFloatExtra(RADIUS_INTENT_KEY, Float.MIN_VALUE);

        for (final String provider : locationManager.getProviders(false)) {
            final Location location = locationManager
                    .getLastKnownLocation(provider);

            if (bestLocation == null) {
                bestLocation = location;
            } else {
                if (location.getAccuracy() < bestLocation.getAccuracy()) {
                    bestLocation = location;
                }
            }
        }

        if (bestLocation != null) {
            if (getDistance(bestLocation) <= radius) {
                inProximity = true;
            } else {
                inProximity = false;
            }
        }

        locationManager.requestLocationUpdates(
                LocationManager.NETWORK_PROVIDER, 0, 0, this);

        return START_STICKY;
    }

    @Override
    public void onLocationChanged(final Location location) {
        final float distance = getDistance(location);

        if ((distance <= radius) && !inProximity) {
            inProximity = true;
            Log.i(TAG, "Entering Proximity");

            final Intent intent = new Intent(
                    ProximityPendingIntentFactory.PROXIMITY_ACTION);
            intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true);
            sendBroadcast(intent);
        } else if ((distance > radius) && inProximity) {
            inProximity = false;
            Log.i(TAG, "Exiting Proximity");

            final Intent intent = new Intent(
                    ProximityPendingIntentFactory.PROXIMITY_ACTION);
            intent.putExtra(LocationManager.KEY_PROXIMITY_ENTERING, true);
            sendBroadcast(intent);
        } else {
            final float distanceFromRadius = Math.abs(distance - radius);

            // Calculate the distance to the edge of the user-defined radius
            // around the target location
            final float locationEvaluationDistance = (distanceFromRadius - location
                    .getAccuracy()) / 2;

            locationManager.removeUpdates(this);
            final float updateDistance = Math
                    .max(1, locationEvaluationDistance);

            String provider;
            if ((distanceFromRadius <= location.getAccuracy())
                    || LocationManager.GPS_PROVIDER.equals(location
                            .getProvider())) {
                provider = LocationManager.GPS_PROVIDER;
            } else {
                provider = LocationManager.NETWORK_PROVIDER;
            }

            locationManager.requestLocationUpdates(provider, 0, updateDistance,
                    this);
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        locationManager.removeUpdates(this);
    }

    @Override
    public void onProviderDisabled(final String provider) {
        // no-op
    }

    @Override
    public void onProviderEnabled(final String provider) {
        // no-op
    }

    @Override
    public void onStatusChanged(final String provider, final int status,
            final Bundle extras) {
        // no-op
    }

    private float getDistance(final Location location) {
        final float[] results = new float[1];

        Location.distanceBetween(latitude, longitude, location.getLatitude(),
                location.getLongitude(), results);

        return results[0];
    }
}
